package net.luohuasheng.bee.proxy.mybatis.executor.criteria;

import net.luohuasheng.bee.jdbc.tools.common.utils.CollectionUtils;
import net.luohuasheng.bee.proxy.core.enums.ConnectType;
import net.luohuasheng.bee.proxy.mybatis.common.enums.FillterType;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

import static net.luohuasheng.bee.proxy.mybatis.common.enums.FillterType.*;

/**
 * @author luohuasheng
 */
public class CriteriaBuilder {
    static String NUM_SPLIT = "_";
    private Criteria criteria;
    private final Set<String> fields = new LinkedHashSet<>();
    private final Map<String, Object> params = new TreeMap<>();
    private ConnectType connectType = ConnectType.AND;
    private ConnectType subConnectType = ConnectType.AND;
    private CriteriaCondition condition;
    private List<CriteriaCondition> conditions = new ArrayList<>();
    private final AtomicInteger num;

    private CriteriaBuilder(Criteria criteria) {
        this.criteria = criteria;
        num = new AtomicInteger(criteria.size());

    }

    private CriteriaBuilder() {
        num = new AtomicInteger(0);
    }

    public static CriteriaBuilder createBuilder() {
        return new CriteriaBuilder();
    }

    public static CriteriaBuilder createBuilder(Criteria criteria) {
        return new CriteriaBuilder(criteria);
    }


    public CriteriaBuilder startWith(String column, String value) {
        addParams(START_WITH, column, escapeSql(value) + "%");
        return this;
    }

    public CriteriaBuilder notStartWith(String column, String value) {
        addParams(NOT_START_WITH, column, escapeSql(value) + "%");
        return this;
    }

    public CriteriaBuilder contain(String column, String value) {
        addParams(CONTAIN, column, "%" + escapeSql(value) + "%");
        return this;
    }

    public CriteriaBuilder notContain(String column, String value) {
        addParams(NOT_CONTAIN, column, "%" + escapeSql(value) + "%");
        return this;
    }

    public CriteriaBuilder endWith(String column, String value) {
        addParams(END_WITH, column, "%" + escapeSql(value));
        return this;
    }

    public CriteriaBuilder notEndWith(String column, String value) {
        addParams(NOT_END_WITH, column, "%" + escapeSql(value));
        return this;
    }

    public CriteriaBuilder between(String column, Object start, Object end) {
        addParams(BETWEEN, column, start, end);
        return this;
    }

    public CriteriaBuilder notBetween(String column, Object start, Object end) {
        Map<String, Object> data = new HashMap<>(2);
        params.put("start", start);
        params.put("end", end);
        addParams(NOT_BETWEEN, column, start, end);
        return this;
    }

    public CriteriaBuilder in(String column, Iterable<?> value) {
        addParams(IN, column, value);
        return this;
    }

    public CriteriaBuilder notIn(String column, Iterable<?> value) {
        addParams(NOT_IN, column, value);
        return this;
    }

    public CriteriaBuilder isBlank(String column) {
        addParams(IS_BLANK, column);
        params.put(column, "");
        return this;
    }

    public CriteriaBuilder isNotBlank(String column) {
        addParams(IS_NOT_BLANK, column);
        params.put(column, "");
        return this;
    }

    public CriteriaBuilder equals(String column, Object value) {
        addParams(EQUALS, column, value);
        return this;
    }

    public CriteriaBuilder notEquals(String column, Object value) {
        addParams(NOT_EQUALS, column, value);
        return this;
    }

    public CriteriaBuilder greater(String column, Object value) {
        addParams(GREATER, column, value);
        return this;
    }

    public CriteriaBuilder notGreater(String column, Object value) {
        addParams(NOT_GREATER, column, value);
        return this;
    }

    public CriteriaBuilder less(String column, Object value) {
        addParams(LESS, column, value);
        return this;
    }

    public CriteriaBuilder notLess(String column, Object value) {
        addParams(NOT_LESS, column, value);
        return this;
    }

    public CriteriaBuilder isNull(String column) {
        addParams(IS_NULL, column);
        return this;
    }

    public CriteriaBuilder isNotNull(String column) {
        addParams(IS_NOT_NULL, column);
        return this;
    }

    public CriteriaBuilder exists(String condition) {
        addParams(EXISTS, condition);
        return this;
    }

    public CriteriaBuilder notExists(String condition) {
        addParams(NOT_EXISTS, condition);
        return this;
    }

    public CriteriaBuilder addConditions(String... conditions) {
        if (conditions != null) {
            for (String s : conditions) {
                addParams(NONE, s);
            }
        }
        return this;
    }

    private void addParams(FillterType fillterType, String column) {
        if (condition == null) {
            condition = new CriteriaCondition(connectType, SubCriteriaCondition.of(subConnectType, fillterType, column));
            conditions.add(condition);
        } else {
            condition.getSubs().add(SubCriteriaCondition.of(subConnectType, fillterType, column));
        }
    }


    private void addParams(FillterType fillterType, String column, Object value) {
        column = column + NUM_SPLIT + num.getAndIncrement();
        params.put(column, value);
        addParams(fillterType, column);

    }

    private void addParams(FillterType fillterType, String column, Object start, Object end) {
        column = column + NUM_SPLIT+ num.getAndIncrement();
        Map<String, Object> data = new HashMap<>(2);
        params.put("start", start);
        params.put("end", end);
        params.put(column, data);
        addParams(fillterType, column);
    }

    /**
     * 使用AND连接并换行
     *
     * @return this
     */
    public CriteriaBuilder and() {
        subConnectType = ConnectType.AND;
        return this;
    }

    public CriteriaBuilder setExpression(boolean expression) {
        if (condition != null && !CollectionUtils.isEmpty(condition.subs)) {
            condition.subs.get(condition.subs.size() - 1).setExpression(expression);
        }
        return this;
    }

    public CriteriaBuilder fields(String... fields) {
        if (fields != null) {
            this.fields.addAll(Arrays.asList(fields));
        }
        return this;
    }

    /**
     * 使用OR连接并换行
     *
     * @return this
     */
    public CriteriaBuilder or() {
        subConnectType = ConnectType.OR;
        return this;
    }

    public CriteriaBuilder andNew() {
        connectType = ConnectType.AND;
        subConnectType = ConnectType.AND;
        conditions = null;
        return this;
    }

    /**
     * 使用OR连接并换行
     *
     * @return this
     */
    public CriteriaBuilder orNew() {
        connectType = ConnectType.OR;
        subConnectType = ConnectType.AND;
        conditions = null;
        return this;
    }


    static class CriteriaCondition {

        private final ConnectType connectType;
        private final List<SubCriteriaCondition> subs = new ArrayList<>();

        public ConnectType getConnectType() {
            return connectType;
        }

        public List<SubCriteriaCondition> getSubs() {
            return subs;
        }

        private CriteriaCondition(ConnectType connectType, SubCriteriaCondition sub) {
            this.connectType = connectType;
            this.subs.add(sub);
        }
    }

    private String escapeSql(String sql) {
        StringBuilder sb = new StringBuilder();
        for (char c : sql.toCharArray()) {
            sb.append('\\').append(c);
        }
        return sb.toString();
    }

    static class SubCriteriaCondition {
        private final ConnectType connectType;
        private final FillterType fillterType;
        private boolean isExpression;
        private final String column;

        private SubCriteriaCondition(ConnectType connectType, FillterType fillterType, String column) {
            this.connectType = connectType;
            this.fillterType = fillterType;
            this.column = column;
        }

        public ConnectType getConnectType() {
            return connectType;
        }

        public FillterType getFillterType() {
            return fillterType;
        }

        public String getColumn() {
            return column;
        }

        public static SubCriteriaCondition of(ConnectType connectType, FillterType fillterType, String column) {
            return new SubCriteriaCondition(connectType, fillterType, column);
        }

        public boolean isExpression() {
            return isExpression;
        }

        public void setExpression(boolean expression) {
            isExpression = expression;
        }
    }

    public Criteria build(String name) {
        if (criteria == null) {
            criteria = new Criteria(name, conditions, fields);
        } else {
            criteria.getConditions().addAll(conditions);
            criteria.getFields().addAll(this.fields);
        }
        criteria.putAll(params);
        return criteria;
    }

    public Criteria build() {
        return build("criteria");
    }
}
